﻿namespace net.zxteam.ZXLexer.Rules
{
	using net.zxteam.ZXLexer.Rules.Internal;
	using net.zxteam.ZXLexer.Tokens;
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Xml.Serialization;

	public abstract class Rule : IRule
	{
		public static IAstRule LoadGrammar(System.IO.Stream grammarStream)
		{
			Schema.grammar pegData = (Schema.grammar)PegXmlSerializer.Instance.Deserialize(grammarStream);

			Dictionary<string, Schema.IIdRules> namedSchemaRules = pegData.Items
				.OfType<Schema.IIdRules>()
				//                .Where(w => w.name != null)
				.ToDictionary(k => k.id);

			return new RuleBuilder(namedSchemaRules, pegData).Build();
		}
		private class RuleBuilder
		{
			private readonly Dictionary<string, Schema.IIdRules> _namedSchemaRules;
			private Dictionary<string, IInnerRule> _namedRules;
			private readonly Schema.grammar _pegData;

			public RuleBuilder(Dictionary<string, Schema.IIdRules> namedSchemaRules, Schema.grammar pegData)
			{
				_namedSchemaRules = namedSchemaRules;
				_pegData = pegData;
			}

			public IAstRule Build()
			{
				var astRule = new AstRule();

				_namedRules = _pegData.Items
					.Cast<Schema.IIdRules>()
					.ToDictionary(k => k.id, e => (IInnerRule)Build(e, astRule));

				astRule.InnerRule= (IInnerRule)Build(_pegData.ast.Item, astRule);

				foreach (var namedRule in _namedRules.Values)
					UpdateReferenceRules(namedRule);
				UpdateReferenceRules(astRule.InnerRule);

				return astRule;
			}

			private void UpdateReferenceRules(IInnerRule rule)
			{
				if (rule is ICollectionRule)
				{
					foreach (var innerRule in ((ICollectionRule)rule).ToArray())
					{
						UpdateReferenceRules(innerRule);
					}
				}
				else if (rule is IContainerRule)
				{
					UpdateReferenceRules(((IContainerRule)rule).InnerRule);
				}
				else if (rule is ReferenceRule)
				{
					string refName = ((ReferenceRule)rule).RefName;
					var refRule = _namedRules[refName];

					IRule parentRule = ((ReferenceRule)rule).Parent;
					if (parentRule is ContainerRuleBase)
					{
						((ContainerRuleBase)parentRule).InnerRule = refRule;
					}
					else if (parentRule is CollectionRuleBase)
					{
						var children = ((CollectionRuleBase)parentRule).Children;

						int index = children.IndexOf(rule);
						children.Insert(index, refRule);
						children.Remove(rule);
					}
					else if (parentRule is AstRule)
					{
						((AstRule)parentRule).InnerRule = refRule;
					}
					else
					{
						throw new InvalidOperationException();
					}
				}
			}

			private IRule Build(object schemaRule, IRule parentRule)
			{
				if (schemaRule == null) throw new ArgumentNullException();

				if (schemaRule is Schema.cardinalRuleType)
				{
					var friendlySchemaRule = ((Schema.cardinalRuleType)schemaRule);
					var rule = new CardinalRule(parentRule);
					rule.InnerRule = (IInnerRule)Build(friendlySchemaRule.Item, rule);
					return rule;
				}
				else if (schemaRule is Schema.charRuleType)
				{
					var friendlySchemaRule = ((Schema.charRuleType)schemaRule);
					var rule = new CharRule(parentRule, friendlySchemaRule.value[0]);
					return rule;
				}
				else if (schemaRule is Schema.choiceRuleType)
				{
					var friendlySchemaRule = ((Schema.choiceRuleType)schemaRule);
					var rule = new ChoiceRule(parentRule);
					var innerRules = friendlySchemaRule.Items
						.Select(s => (IInnerRule)Build(s, rule));
					foreach (var innerRule in innerRules) rule.Add(innerRule);
					return rule;
				}
				else if (schemaRule is Schema.indentationRuleType)
				{
					var friendlySchemaRule = ((Schema.indentationRuleType)schemaRule);
					var rule = new IndentRule(parentRule);
					rule.InnerRule = (IInnerRule)Build(friendlySchemaRule.Item, rule);
					return rule;

				}
				else if (schemaRule is Schema.rangeRuleType)
				{
					var friendlySchemaRule = ((Schema.rangeRuleType)schemaRule);
					var rule = new CharRangeRule(parentRule, friendlySchemaRule.from[0], friendlySchemaRule.to[0]);
					return rule;
				}
				else if (schemaRule is Schema.referenceRuleType)
				{
					var friendlySchemaRule = ((Schema.referenceRuleType)schemaRule);
					return new ReferenceRule(parentRule) { RefName = friendlySchemaRule.@ref };
				}
				else if (schemaRule is Schema.sequenceRuleType)
				{
					var friendlySchemaRule = ((Schema.sequenceRuleType)schemaRule);
					var rule = new SequenceRule(parentRule);
					var innerRules = friendlySchemaRule.Items
						.Select(s => (IInnerRule)Build(s, rule));
					foreach (var innerRule in innerRules) rule.Add(innerRule);
					return rule;
				}
				else if (schemaRule is Schema.stringRuleType)
				{
					var friendlySchemaRule = ((Schema.stringRuleType)schemaRule);
					var rule = new StringRule(parentRule, friendlySchemaRule.value);
					return rule;
				}
				else
				{
					throw new NotSupportedException(schemaRule.GetType().FullName);
				}
			}

			private class ReferenceRule : InnerRule
			{
				public ReferenceRule(IRule parentRule)
					: base(parentRule)
				{
				}

				public override IToken Accept(IRuleVisitor visitor) { throw new NotSupportedException(); }

				public string RefName { get; set; }
			}
		}

		public abstract IToken Accept(IRuleVisitor visitor);

		private class PegXmlSerializer : XmlSerializer
		{
			public static readonly PegXmlSerializer Instance = new PegXmlSerializer();
			static PegXmlSerializer() { }

			private PegXmlSerializer() : base(typeof(Schema.grammar)) { }
		}
	}
}